package testApplets;

import processing.core.PApplet;
import processing.core.PGraphics3D;
import simpleGeom.Bounds3D;
import simpleGeom.Box3D;
import simpleGeom.Grid3DFloat;
import simpleGeom.MMesh;
import simpleGeom.PGraphicsHelper;
import simpleGeom.SimpleMCube;
import simpleGui.Gui;
import simpleGui.GuiSpinnerGroup;
import simpleGui.PCamera3D;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;


import processing.core.PVector;

public class TestVoxelise extends PApplet {
	
	MMesh mesh;
	Box3D bounds;	//float[] values;
	SimpleMCube mc=new SimpleMCube();
	float[] boxInside;
	PCamera3D cam;
	boolean flagUpdateInsideOutside;
	boolean flagExport;
	Grid3DFloat grid;
	int displayMode=0;
	String path="/Users/caad/Desktop/Grotto2/";
	String objectPath="/Users/caad/Desktop/Grotto2/testOBJs/forBeni.obj";
	String streamPath=path+"streamDistance/";
	String valuePath="/Users/caad/Desktop/Grotto2/scanValues.txt";
	
	Gui gui;
	GuiSpinnerGroup sx12,sy1,sy2,sz1,sz2;
	
	public void setup() {
		
		setupGui();
		size(1000, 700, P3D);
		cam=new PCamera3D(this);
		println("float array initialized");
		// import mesh
		mesh = new MMesh();
		mesh.loadOBJ(objectPath);
		mesh.triangulateQuads();
	
		// can be turned of after first time
		/*initStream();
		vStream.initBuffer(100);
		println("start save");
		vStream.saveBuffer(0);
		println("saved");
		vStream.loadBuffer(0);
		println("loaded");*/
		//vStream.initAllFiles(1000);
		
		//scan(0,100);
		//vStream.saveAllValues();
	
		//
		
		/*
		
		setupGui();*/
		startTest(200,false);
	}
	public void startTest(int resolution,boolean doScan){
		if (doScan){
			scan(resolution);
			grid.saveValues(valuePath);
		}
		else{
			grid=new Grid3DFloat(valuePath);
			boxInside = new float[] { grid.nX * 0.2f,grid.nY * 0.2f, grid.nZ * 0.2f, grid.nX * 0.8f,
					grid.nY * 0.8f, grid.nZ * 0.9f };
			
		}
		defineInsideOutside();
		mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values, grid.dim, grid.dim, grid.x1, grid.y1,
				grid.z1, null);
	}
	public void setupGui(){
		int py=20;
		int pyO=10;
		int x1=10;
		int x2=50;
		gui=new Gui(this,this.createFont("Arial", 12));
		gui.addGuiLabel(x1,py+pyO,"x1x2: ");
		sx12=gui.addGuiSpinnerGroup(x2,py,100,0,999);
		py+=25;
		gui.addGuiLabel(x1,py+pyO,"y1: ");
		sy1=gui.addGuiSpinnerGroup(x2,py,100,0,999);
		py+=25;
		gui.addGuiLabel(x1,py+pyO,"y2: ");
		sy2=gui.addGuiSpinnerGroup(x2,py,900,0,999);
		py+=25;
		gui.addGuiLabel(x1,py+pyO,"z1: ");
		sz1=gui.addGuiSpinnerGroup(x2,py,100,0,999);
		py+=25;
		gui.addGuiLabel(x1,py+pyO,"z2: ");
		sz2=gui.addGuiSpinnerGroup(x2,py,900,0,999);
	}
	VoxelStreamFloat vStream;
	public void initStream(){
		
		bounds=mesh.getBounds();
		println(bounds);
		float dim = bounds.getDimX() / 1600f;
		bounds=bounds.getOffsetBox(dim*2);
		float dimX = bounds.getDimX() ;
		float dimY = bounds.getDimY() ;
		float dimZ = bounds.getDimZ() ;
		dim = dimX / 1600f;
		int nX = (int) (dimX / dim) + 2;
		int nY = (int) (dimY / dim) + 2;
		int nZ = (int) (dimZ / dim) + 2;
		println("nX: " + nX + "  nY: " + nY + "  nZ: " + nZ);
		vStream=new VoxelStreamFloat(streamPath,dim,bounds.x1,bounds.y1,bounds.z1,nX,nY,nZ);
	}
	public void scanStream(int y1,int y2){
		
		vStream.initBuffer((int)(y2-y1));
		grid=vStream.grid;
		// scanning
		MMesh.scanMeshDistance(mesh.xc, mesh.yc, mesh.zc, mesh.triangles,
				bounds.x1,bounds.y1,bounds.z1, grid.nX, grid.nY, grid.nZ, grid.dim, grid.dim * 1.5f,
				grid.values);
	}
	public void scan(int resolution){
		bounds=mesh.getBounds();
		float dim = bounds.getDimX() / resolution;
		bounds=bounds.getOffsetBox(dim*2);
		float dimX = bounds.getDimX() ;
		float dimY = bounds.getDimY() ;
		float dimZ = bounds.getDimZ() ;
		dim = dimX / resolution;
		int nX = (int) (dimX / dim) + 2;
		int nY = (int) (dimY / dim) + 2;
		int nZ = (int) (dimZ / dim) + 2;
		println("nX: " + nX + "  nY: " + nY + "  nZ: " + nZ);
		grid=new Grid3DFloat(nX,nY,nZ);
		grid.x1=bounds.x1;
		grid.y1=bounds.y1;
		grid.z1=bounds.z1;
		grid.dim=dim;
		// scanning
		MMesh.scanMeshDistance(mesh.xc, mesh.yc, mesh.zc, mesh.triangles,
				bounds.x1, bounds.y1, bounds.z1, nX, nY, nZ, dim, dim * 1.5f,
				grid.values);
	}

	
	public void defineInsideOutside() {
		grid=new Grid3DFloat(valuePath);
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boxInside[0] > x || boxInside[3] < x
							|| boxInside[1] > y || boxInside[4] < y
							|| boxInside[2] > z || boxInside[5] < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = 0;
					}
					else{
						if (grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] <0){
							grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z]*=-1;
						}
						
					}
				}
			}
		}
		
		println("start reach");
		int index=grid.getIndex((int)(grid.nX*0.5f), (int)(grid.nY*0.5f),(int)( grid.nZ*0.5f));
		Grid3DFloat.reached(grid.values, grid.nX, grid.nY, grid.nZ, index,grid.dim);
		println("end reach");
		
		for (int i = 0; i < grid.values.length; i++) {
			if (grid.values[i] < 0) 
				grid.values[i] *= -1;
			 else if (grid.values[i] > grid.dim) 
				 grid.values[i] = 0;
		}
		
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boxInside[0] > x || boxInside[3] < x
							|| boxInside[1] > y || boxInside[4] < y
							|| boxInside[2] > z || boxInside[5] < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = grid.dim * 2;
					}
				}
			}
		}
	}
	
	public void draw() {
		boxInside = new float[] { grid.nX * sx12.getValue()/1000f,grid.nY *sy1.getValue()/1000f, grid.nZ *sz1.getValue()/1000f, grid.nX * (1000-sx12.getValue())/1000f,
				grid.nY *sy2.getValue()/1000f, grid.nZ *sz2.getValue()/1000f };
		
		if (flagUpdateInsideOutside){
			flagUpdateInsideOutside=false;
			defineInsideOutside();
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values, grid.dim, grid.dim, grid.x1, grid.y1,
					grid.z1, null);
		}
		if (flagExport){
			flagExport=false;
			mc.saveObj(this.selectOutput());
		}
		drawData();
		drawGui();
	}
	
	public void drawData(){
		background(0);
		pushMatrix();
		
		cam.apply((PGraphics3D)g);
		
		rotateY(PI);
		hint(ENABLE_DEPTH_TEST);
		pointLight(170,170,170,10,10,10);
		pointLight(170,170,170,-10,10,-10);
		PCamera3D.drawWorldAxis3D(g, 100);
		float dimX = grid.x2() - grid.x1();
		scale(3 / dimX);
		if (displayMode==0){
		noFill();
		stroke(0, 255, 0);
		//mesh.draw((PGraphics3D) g);
		beginShape(TRIANGLES);
		for (int i=0;i<mesh.triangles.length;i+=18){
			for (int j = 0; j <3; j++) {
				vertex(mesh.xc[mesh.triangles[i+j]],mesh.yc[mesh.triangles[i+j]],mesh.zc[mesh.triangles[i+j]]);
			}
		}
		endShape();
		}
		noStroke();
		fill(255);
		mc.draw((PGraphics3D) g);
		noFill();
		stroke(0,0,255);
		translate(grid.x1,grid.y1,grid.z1);
		scale(grid.dim);
		translate((boxInside[0]+boxInside[3])*0.5f,(boxInside[1]+boxInside[4])*0.5f,(boxInside[2]+boxInside[5])*0.5f);
		box(boxInside[3]-boxInside[0],boxInside[4]-boxInside[1],boxInside[5]-boxInside[2]);
		popMatrix();
	}
	
	public void drawGui(){
		noLights();
		hint(DISABLE_DEPTH_TEST);
		fill(10);
		stroke(100);
		rect(0,0,100,height);
		gui.display(g);
	}
	
	
	
	public void keyPressed(){
		if (key=='u'){
			flagUpdateInsideOutside=true;
		}
		if (key=='d'){
			displayMode++;
			displayMode=displayMode%3;
		}
		if (key=='e'){
			flagExport=true;;
		}
	}
	
}
